#include "array.hpp"
#include "utils.hpp"

#include <iostream>
#include <sstream>
#include <fstream>
#include <ctime>
#include <cmath>
#include <map>

Variable bi_array(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if(argc==1) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	if( (argv[1].type != tString) && (argv[1].type != tVarname) ) {
		throw_error(ERROR_ARGUMENTS);
	}
	if(argv[1].str=="make") {
		if(argc != 4) {
			cout << "Usage: (array make name count)\n";
			return ret;
		}
		if( argv[2].type != tVarname ) {
			throw_error(ERROR_ARGUMENTS);
		}
		Variable count = argv[3];
		env.verify(count);
		if( count.type != tNumeric ) {
			throw_error(ERROR_ARGUMENTS);
		}
		if( (int)count.i < 0 ) {
			throw_error(ERROR_ARRAYBOUNDS);
		}
		env.getvar(argv[2].str).makearr((int)count.i);
		ret=env.getvar(argv[2].str);
	} else if(argv[1].str=="index") {
		if(argc != 4) {
			cout << "Usage: (array index name count)\n";
			return ret;
		}
		if( (argv[2].type != tVarname) && (argv[2].type != tArray) ) {
			throw_error(ERROR_ARGUMENTS);
		}
		Variable a = argv[3];
		Variable lval = argv[2];
		env.verify(a);
		if( a.type != tNumeric ) {
			throw_error(ERROR_ARGUMENTS);
		}
		if(lval.type == tVarname) {
			if( ((int)argv[3].i> (int)env.getvar(argv[2].str).arr.size()) || (int)argv[3].i<0) {
				throw_error(ERROR_ARRAYBOUNDS);
			}
			ret = env.getvar(argv[2].str).arr[(int)a.i];
		} else {
			if( ((int)argv[3].i > (int)argv[2].arr.size()) || (int)argv[3].i<0 ) {
				throw_error(ERROR_ARRAYBOUNDS);
			}
			ret = argv[2].arr[(int)a.i];
		}
	} else if(argv[1].str=="size") {
		if(argc != 3) {
			cout << "Usage: (array size name)\n";
			return ret;
		}
		if(argv[2].type != tVarname) {
			throw_error(ERROR_ARGUMENTS);
		}
		ret = env.getvar(argv[2].str).arr.size();
	} else if(argv[1].str=="set") {
		if(argc != 6) {
			cout << "Usage: (array set name count operation rval)\n";
			return ret;
		}
		if(argv[2].type != tVarname) {
			throw_error(ERROR_ARGUMENTS);
		}
		Variable a = argv[3];
		env.verify(a);
		if( (a.type != tNumeric)||(argv[4].type != tOpcode) || (env.getvar(argv[2].str).type != tArray) ) {
			throw_error(ERROR_ARGUMENTS);
		}
		if( ((unsigned)a.i>env.getvar(argv[2].str).arr.size()) || (int)a.i<0) {
			throw_error(ERROR_ARRAYBOUNDS);
		}
		Variable rval = argv[5];
		env.verify(rval);
		Variable* lval = &env.getvar(argv[2].str).arr[(int)a.i];
		Variable op = argv[4];
		if( op.str=="=" ) {
			lval->clear();
			*lval = rval;
		} else if( (op.str=="+") || (op.str=="+=") ) {
			if(lval->type == tNumeric && rval.type == tNumeric) {
				*lval = lval->i + rval.i;
			} else if(lval->type == tArray) {
				if(rval.type==tArray) {
					lval->arr = vectorconcat(lval->arr,rval.arr);
				} else {
					lval->arr.push_back(rval);
				}
			} else {
				*lval = lval->str + rval.str;
			}
		} else if( (op.str=="-") || (op.str=="-=") ) {
			*lval = lval->i - rval.i;
		} else if( (op.str=="*") || (op.str=="*=") ) {
			*lval = lval->i * rval.i;
		} else if( (op.str=="/") || (op.str=="/=") ) {
			if(rval.i!=0) {
				*lval = lval->i / rval.i;
			}
		} else if( (op.str=="%") || (op.str=="%=") ) {
			*lval = fmod(lval->i,rval.i);
		}else if( (op.str=="^") || (op.str=="^=") ) {
			*lval = pow(lval->i,rval.i);
		} else {
			throw_error(ERROR_OPERATION);
		}
		ret = *lval;
	} else if(argv[1].str=="build") {
		Variable tmp;
		if(argc==2) {
			throw_error(ERROR_ARGUMENTCOUNT);
		}
		ret.makearr(0);
		for(unsigned i=2;i<argv.size();i++) {
			tmp = argv[i];
			env.verify(tmp);
			ret.arr.push_back(tmp);
		}
	} else if(argv[1].str=="push") {
		if(argc != 4) {
			cout << "Usage: (array push name value)\n";
			return ret;
		}
		if( argv[2].type != tVarname ) {
			throw_error(ERROR_ARGUMENTS);
		}
		Variable value = argv[3];
		env.verify(value);
		env.getvar(argv[2].str).arr.push_back(value);
		ret=env.getvar(argv[2].str);
	} else if(argv[1].str=="pop") {
		if(argc != 3) {
			cout << "Usage: (array pop name)\n";
			return ret;
		}
		if( argv[2].type != tVarname ) {
			throw_error(ERROR_ARGUMENTS);
		}
		if(env.getvar(argv[2].str).arr.size() != 0) {
			ret = env.getvar(argv[2].str).arr.back();
			env.getvar(argv[2].str).arr.pop_back();
		}
	} else if(argv[1].str=="dequeue") {
		if(argc != 3) {
			cout << "Usage: (array dequeue name)\n";
			return ret;
		}
		if( argv[2].type != tVarname ) {
			throw_error(ERROR_ARGUMENTS);
		}
		Variable tmp = env.getvar(argv[2].str);
		if(tmp.arr.size() != 0) {
			ret = tmp.arr.front();
			for(unsigned i=0;i<(tmp.arr.size()-1);i++) {
				tmp.arr[i] = tmp.arr[i+1];
			}
			tmp.arr.pop_back();
			env.getvar(argv[2].str) = tmp;
		}
	} else if(argv[1].str=="peek") {
		if(argc != 3) {
			cout << "Usage: (array peek name)\n";
			return ret;
		}
		if( argv[2].type != tVarname ) {
			throw_error(ERROR_ARGUMENTS);
		}
		ret = env.getvar(argv[2].str).arr.back();
	} else if(argv[1].str=="build") {
		if(argc < 3) {
			cout << "Usage: (array build args ...)\n";
			return ret;
		}
		ret.makearr(0);
		Variable(tmp);
		for(int i=2;i<argc;i++) {
			tmp = argv[i];
			env.verify(tmp);
			ret.arr.push_back(tmp);
		}
	} else {
		throw_error(ERROR_OPERATION);
	}
	return ret;
}

Variable bi_new(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	int argc = (int)argv.size();
	if(argc < 2 || argc > 3) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	Variable cmd = argv[1];
	if(cmd.type != tVarname && cmd.type != tString) {
		throw_error(ERROR_ARGUMENTS);
	}
	if(cmd.str=="array") {
		Variable size;
		if(argc==2) {
			size = 0;
		} else if(argc==3) {
			size = argv[2];
			env.verify(size);
		} else {
			throw_error(ERROR_ARGUMENTCOUNT);
		}
		ret.makearr((int)size.i);
	} else if(cmd.str == "int") {
		double d=0;
		if(argc == 3) {
			Variable tmp;
			tmp = argv[2];
			env.verify(tmp);
			d = tmp.i;
		}
		ret = d;
	} else if(cmd.str == "string") {
		string s="";
		if(argc == 3) {
			Variable tmp;
			tmp = argv[2];
			env.verify(tmp);
			s = tmp.str;
		}
		ret = s;
	} else if(cmd.str == "inline") {
		string s="";
		if(argc == 3) {
			Variable tmp;
			tmp = argv[2];
			env.verify(tmp);
			s = tmp.str;
		}
		ret = s;
		ret.type = tInline;
	} else if(cmd.str == "char") {
		string s="";
		if(argc == 3) {
			Variable tmp;
			tmp = argv[2];
			env.verify(tmp);
			s = char(tmp.i);
		}
		ret = s;
	} else {
		if(env.types.find(cmd.str) != env.types.end()) {
			ret.makearr(env.types[cmd.str].size());
			ret.type = tCustomType;
			ret.str = cmd.str;
			ret.i = ret.arr.size();
		} else {
			ret = Variable();
		}
	}
	return ret;
}

Variable bi_delete(vector<Variable>& argv, Environment& env)
{
	Variable ret;
	string vname;
	string op;
	int argc = (int)argv.size();
	if(argc==3) {
		if( (argv[1].type != tString) && (argv[1].type != tVarname) ) {
			throw_error(ERROR_ARGUMENTS);
		}
		if(argv[2].type != tVarname) {
			throw_error(ERROR_ARGUMENTS);
		}
		vname = argv[2].str;
		op = argv[1].str;
	} else if(argc==2) {
		if(argv[1].type != tVarname) {
			throw_error(ERROR_ARGUMENTS);
		}
		vname = argv[1].str;
		op = "var";
		if(vname=="all") {
			op="all";
		}
	}
	if( op == "alias" ) {
		ret = env._alias.erase(vname);
	} else if( op == "all" ) {
		env._alias.clear();
		ret = true;
	} else {
		env.getvar(vname).clear();
		ret = true;
	}
	return ret;
}

Variable bi_matches(vector<Variable>& argv, Environment& env)
{
	Variable ret = false;
	int argc = (int)argv.size();
	if(argc != 3) {
		throw_error(ERROR_ARGUMENTCOUNT);
	}
	Variable source = argv[1];
	Variable dest = argv[2];
	env.verify(source);
	env.verify(dest);
	if(dest.type != tArray) {
		throw_error(ERROR_ARGUMENTS);
	}
	for(unsigned i=0;i<dest.arr.size();i++) {
		if(source==dest.arr[i]) {
			ret = true;
		}
	}
	return ret;
}
